#include "Dri_SPI.h"

/**
 * @brief SPI初始化
 *
 */
void Dri_SPI_Init(void)
{
    // 1. 时钟使能 ------------------------------
    // 1.1 对 SPI1 时钟使能
    RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
    // 1.2 对 GPIOC 时钟使能
    RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
    // 1.3 对 GPIOA 时钟使能
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;

    // 2. 引脚模式设置 ---------------------------
    // 2.1 设置 SS(PC13) 为通用推挽输出模式, MODE=11,CNF=00
    GPIOC->CRH |= GPIO_CRH_MODE13;
    GPIOC->CRH &= ~GPIO_CRH_CNF13;
    // 2.2 设置 SCLK(PA5)为复用推挽输出模式, MODE=11,CNF=10
    GPIOA->CRL |= GPIO_CRL_MODE5;
    GPIOA->CRL |= GPIO_CRL_CNF5_1;
    GPIOA->CRL &= ~GPIO_CRL_CNF5_0;
    // 2.3 设置 MOSI(PA7) 为复用推挽输出模式, MODE=11,CNF=10
    GPIOA->CRL |= GPIO_CRL_MODE7;
    GPIOA->CRL |= GPIO_CRL_CNF7_1;
    GPIOA->CRL &= ~GPIO_CRL_CNF7_0;
    // 2.4 设置 MISO(PA6) 为浮空输入模式(也可以是复用推挽输出), MODE=00,CNF=01(默认值)
    GPIOA->CRL &= ~GPIO_CRL_MODE6;
    GPIOA->CRL &= ~GPIO_CRL_CNF6_1;
    GPIOA->CRL |= GPIO_CRL_CNF6_0;

    // 3. 默认拉高片选信号 --------------------------
    SS_HIGH;

    // 4. SPI1 配置 --------------------------------
    // 4.1 设置 SCLK 时钟频率为 18MHz，需要对 APB2 频率进行 4 分频 (CR1.BR=001)
    SPI1->CR1 &= ~SPI_CR1_BR;  // 3个位全部置0
    SPI1->CR1 |= SPI_CR1_BR_0; // 第0位设置为1
    // 4.2 设置使用 SPI 的模式0，CPOL=0, CPHA=0 (CR1.CPOL=0, CR1.CPHA=0)
    SPI1->CR1 &= ~SPI_CR1_CPOL;
    SPI1->CR1 &= ~SPI_CR1_CPHA;
    // 4.3 设置数据帧格式为 8 位 (CR1.DFF=0)
    SPI1->CR1 &= ~SPI_CR1_DFF;
    // 4.4 设置高位先行 (CR1.LSBFIRST=0)
    SPI1->CR1 &= ~SPI_CR1_LSBFIRST;
    // 4.5 设置NSS为软件控制并强制拉高 (CR1.SSM=1; CR1.SSI=1) (目的：MSTR、SPE 保持置位的前提是 NSS 保持高电平)
    SPI1->CR1 |= SPI_CR1_SSM;
    SPI1->CR1 |= SPI_CR1_SSI;
    // 4.6 设置为 SPI 的主模式 (CR1.MSTR=1)
    SPI1->CR1 |= SPI_CR1_MSTR;
    // 4.7 设置 SPI 使能 (CR1.SPE=1)
    SPI1->CR1 |= SPI_CR1_SPE;
}

/**
 * @brief 开始通信
 *
 */
void Dri_SPI_Start(void)
{
    // 拉低片选信号
    SS_LOW;
}

/**
 * @brief 停止通信
 *
 */
void Dri_SPI_Stop(void)
{
    // 拉高片选信号
    SS_HIGH;
}

/**
 * @brief 交换一个字节
 *
 * @param byte 发送的字节
 * @return uint8_t 接收到的字节
 */
uint8_t Dri_SPI_SwapByte(uint8_t byte)
{
    // 等待发送缓冲区为空
    while ((SPI1->SR & SPI_SR_TXE) == 0)
        ;

    // 将要发送的数据写入发送缓冲区，同时写DR的操作会清除TXE标志位
    SPI1->DR = byte;

    // 等待接收缓冲区非空(等待数据传输完毕)
    while ((SPI1->SR & SPI_SR_RXNE) == 0)
        ;

    // 读取接收缓冲区中的数据并返回，同时读DR的操作会清除RXNE标志位
    return SPI1->DR;
}
